package com.atguigu.gmall.index.service;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.bean.ResponseVo;
import com.atguigu.gmall.index.annotation.GmallCache;
import com.atguigu.gmall.index.feign.GmallPmsClient;
import com.atguigu.gmall.index.utils.DistributedLock;
import com.atguigu.gmall.pms.entity.CategoryEntity;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RCountDownLatch;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class IndexService {

    @Autowired
    private GmallPmsClient pmsClient;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private DistributedLock distributedLock;

    @Autowired
    private RedissonClient redissonClient;

    private static final String KEY_PREFIX = "index:cates:";

    public List<CategoryEntity> queryLvl1Cates() {
        ResponseVo<List<CategoryEntity>> responseVo = this.pmsClient.queryCategoriesByPid(0l);
        return responseVo.getData();
    }

    @GmallCache(prefix = KEY_PREFIX,timeout = 129600,random = 14400,lock = "index:cates:lock:")
    public List<CategoryEntity> queryLvl23CatesByPid(Long pid) {
        //2、查询数据库  远程调用  放入缓存
        ResponseVo<List<CategoryEntity>> responseVo = this.pmsClient.queryLvl2WithSubsByPid(pid);
        return responseVo.getData();
    }

    public List<CategoryEntity> queryLvl23CatesByPid2(Long pid) {
        //1、查询缓存
        String json = this.redisTemplate.opsForValue().get(KEY_PREFIX + pid);
        if (StringUtils.isNotBlank(json)) {
            //将什么转换 + 根据什么转换
            return JSON.parseArray(json, CategoryEntity.class);
        }

        //上公平锁（防止缓存击穿）
        //为了区分和别的锁，避免同时锁住，所以给这个锁取别名。
        RLock fairLock = this.redissonClient.getFairLock("index:cates:lock" + pid);
        fairLock.lock();

        try {
            //再次查询缓存，因为在获取锁的过程中，可能有其他请求已经把数据放入缓存
            String json2 = this.redisTemplate.opsForValue().get(KEY_PREFIX + pid);
            if (StringUtils.isNotBlank(json2)) {
                //将什么转换 + 根据什么转换
                return JSON.parseArray(json2, CategoryEntity.class);
            }

            //2、查询数据库  远程调用  放入缓存
            ResponseVo<List<CategoryEntity>> responseVo = this.pmsClient.queryLvl2WithSubsByPid(pid);
            List<CategoryEntity> categories = responseVo.getData();
            //为了解决缓存穿透，数据即使为null 也缓存
            if (CollectionUtils.isEmpty(categories)) {
                //如果数据为null，则缓存null，时间为5分钟
                this.redisTemplate.opsForValue().set(KEY_PREFIX + pid, JSON.toJSONString(categories), 5, TimeUnit.MINUTES);
            } else {
                //为了解决缓存雪崩，给缓存时间添加随机值
                this.redisTemplate.opsForValue().set(KEY_PREFIX + pid, JSON.toJSONString(categories), 90 + new Random().nextInt(10), TimeUnit.DAYS);
            }
            return categories;
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            fairLock.unlock();
        }
        return null;    //出现异常就返回null
    }

    public void testLock() {
        //加锁
        RLock lock = this.redissonClient.getLock("lock");
        lock.lock();
        try {
            String num = this.redisTemplate.opsForValue().get("num");
            if (StringUtils.isNotBlank(num)) {
                this.redisTemplate.opsForValue().set("num", "1");
            }
            this.redisTemplate.opsForValue().set("num", String.valueOf(Integer.parseInt(num) + 1));
            this.testSubLock2();
/*            try {     //测试是否会重置锁过期时间
                TimeUnit.SECONDS.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
        } finally {
            //解锁
            lock.unlock();
        }
    }

    private void testSubLock2(){
        RLock lock = this.redissonClient.getLock("lock");
        lock.lock();
        System.out.println("2...........");
        lock.unlock();
    }

    public void testSubLock(String uuid) {
        this.distributedLock.tryLock("lock", uuid, 30);
        System.out.println("1........");
        this.distributedLock.unLock("lock", uuid);
    }


    public void testLock2() {
        //加锁    setIfAbsent 相当于 redis中的setnx，具有独占、排他性。（一次处理一个业务操作，成功之后，别的请求才能进入）
        //  timeout相当于else里面的expire，设置过期时间防死锁
        String uuid = UUID.randomUUID().toString();
        Boolean flag = this.redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
        if (!flag) {
            try {
                // 获取锁失败则重试
                Thread.sleep(100);
                this.testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            //this.redisTemplate.expire("lock",20,TimeUnit.SECONDS);
            String num = this.redisTemplate.opsForValue().get("num");
            if (StringUtils.isNotBlank(num)) {
                this.redisTemplate.opsForValue().set("num", "1");
            }
            this.redisTemplate.opsForValue().set("num", String.valueOf(Integer.parseInt(num) + 1));
//            //解锁  建议放到finally种 (已替换为通过过期时间来解锁)
////            if (StringUtils.equals(uuid,this.redisTemplate.opsForValue().get("lock"))){
////                this.redisTemplate.delete("lock");
////            }
            String script = "if redis.call('get',KEYS[1]) == ARGV[1] " +
                    "then " +
                    "return redis.call('del',KEYS[1]) \n" +
                    "else " +
                    "return 0 " +
                    "end";
            this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList("lock"), uuid);
        }
    }

    public void testLock3() {
        //加锁
        String uuid = UUID.randomUUID().toString();
        Boolean lock = this.distributedLock.tryLock("lock", uuid, 30);
        if (lock) {
            String num = this.redisTemplate.opsForValue().get("num");
            if (StringUtils.isNotBlank(num)) {
                this.redisTemplate.opsForValue().set("num", "1");
            }
            this.redisTemplate.opsForValue().set("num", String.valueOf(Integer.parseInt(num) + 1));

            //this.testSubLock(uuid);

            try {
                TimeUnit.SECONDS.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //解锁
            this.distributedLock.unLock("lock", uuid);
        }
    }

    public void testRead() {
        RReadWriteLock rwLock = this.redissonClient.getReadWriteLock("rwLock");
        //加锁并给锁设置过期时间（自动解锁）
        rwLock.readLock().lock(10,TimeUnit.SECONDS);

        //rwLock.readLock().unlock();   //手动解锁
    }

    public void testWrite() {
        RReadWriteLock rwLock = this.redissonClient.getReadWriteLock("rwLock");
        //加锁并给锁设置过期时间（自动解锁）
        rwLock.writeLock().lock(10,TimeUnit.SECONDS);
    }

    public void testLatch() {
        try {
            RCountDownLatch cdl = this.redissonClient.getCountDownLatch("cdl");
            //设置同学人数
            cdl.trySetCount(6);
            cdl.await();
            // TODO: 锁门操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    public void testCountDown() {
        RCountDownLatch cdl = this.redissonClient.getCountDownLatch("cdl");
        cdl.countDown();
        // TODO： 同学出门操作
    }
}
